/***************************************************************************
 * This code and information is provided "as is" without warranty of any   *
 * kind, either expressed or implied, including but not limited to the     *
 * implied warranties of merchantability and/or fitness for a particular   *
 * purpose.                                                                *
 *                                                                         *
 * Copyright (C) 2008 Teridian SemiConductor, Corp. All Rights Reserved.   *
 *                                                                         *
 ***************************************************************************/
//**************************************************************************//
//                                                                          // 
//  DESCRIPTION: TSCP-CCID                                                  //
//               PacketParser.cs                                            //
//  PacketParser is an object that interprets the message received from     //
//  serial port. The input for this object is a message string              //
//                                                                          //
//  AUTHOR:  SD                                                             //
//                                                                          //
//  HISTORY: 2007 SEPTEMBER 26; First Version Released 1.0                  //
//           2007 DECEMBER  14; Version 2.00 Release                        //
//                                                                          //
//**************************************************************************//
/***************************************************************************
 * This code was adapted from PacketParser.cs which was originally written *
 * by Teridian SemiConductor, Corp. It has been adapted as needed to allow *
 * use for the GemCore Serial Lite Pro evaluation board. It has also been  *
 * modified to be used in the .NET Micro Framework.                        *
 * LAST MODIFIED: 2010 April 23                                            *
 * Copyright (C) 2009 Gemalto                                              *
 ***************************************************************************/
/***************************************************************************
 *  PacketParser.cs is part of the PC/SC Micro API for the .NET Micro      *
 *  Framework.                                                             *
 *                                                                         *
 *  The PC/SC Micro API for the .NET Micro Framework is free software:     *
 *  you can redistribute it and/or modify it under the terms of the GNU    *
 *  General Public License as published by the Free Software Foundation,   *
 *  either version 2 of the License, or (at your option) any later version.*
 *                                                                         *
 *  The PC/SC Micro API for the .NET Micro Framework is distributed in     *
 *  the hope that it will be useful, but WITHOUT ANY WARRANTY; without even*
 *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR    *
 *  PURPOSE.  See the GNU General Public License for more details.         *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with the PC/SC Micro API for the .NET Micro Framework. If not,   *
 *  see <http://www.gnu.org/licenses/>.                                    *
 ***************************************************************************/
using System;
using System.Text;

namespace PCSCMicro
{
    sealed class PacketParser
    {
        // Definition of acknowledge bytes
        const string ACK = "60";
        const string NACK = "E0";

        // Definition of statuses
        const string UNKNOWN_CMD        = "01";
        const string OP_IMPOSSIBLE      = "02";
        const string BAD_NUM_ARGS       = "03";
        const string BAD_CMD_CODE       = "04";
        const string RESPONSE_TOO_LONG  = "05";
        const string BAD_ATR_TS         = "10";
        const string MSG_TOO_LONG       = "12";
        const string BYTE_READ_ERR      = "13";
        const string POWERED_DOWN       = "15";
        const string BAD_NUM_PARAMS     = "1B";
        const string WRITING_OVERLAP    = "1C";
        const string BAD_ATR_TCK        = "1D";
        const string WRITE_PROTECTED    = "1E";
        const string INCORRECT_DATA     = "1F";
        const string RESET_RESPONSE_ERR = "A0";
        const string BAD_PROTOCOL       = "A1";
        const string CARD_MALFUNCTION   = "A2";
        const string BAD_PARITY         = "A3";
        const string CARD_ABORT         = "A4";
        const string READER_ABORT       = "A5";
        const string RESYNCH_SUCCESS    = "A6";
        const string BAD_PTS            = "A7";
        const string CMD_NOT_SUPPORTED  = "B0";
        const string OTHER_KEY_PRESSED  = "CF";
        const string BAD_PROCEDURE      = "E4";
        const string CARD_INTERUPTED    = "E5";
        const string SW1_SW2            = "E7";
        const string CARD_REMOVED       = "F7";
        const string SHORT_CIRCUIT      = "F8";
        const string CARD_MISSING       = "FB";

        // Definitions of response locations
        const int statusWordLen = 1;
        const int statusWordStrLen = statusWordLen*2;
        const int LenLocation = 4;
        
        // Potential error types
        internal enum ErrorType
        {
            None, NoResponse, Timeout, NACK, IllegalMessage,
            UnknownCommand, OperationImpossible, NumberArgs,
            BadCommandCode, ResponseLong, BadATRTS, MessegeLong,
            ByteRead, PoweredDown, NumberParams, WritingOverlap,
            BadATRTCK, WriteProtected, IncorrectData, ResetResponse,
            BadProtocol, CardMalfunction, BadParity, CardAbort,
            ReaderAbort, RESYNCHSuccess, BadPTS, CommandNotSupported,
            KeyPressed, BadProcedure, CardInterupted, SW1SW2,
            CardRemoved, ShortCircuit, CardMissing, Unknown
        }

        // Definition of accessible variables
        string Message;
        internal string ResMsg;
        internal string ErrorMessage;
        internal string statusWord;
        internal string resp;
        internal int respLen;
        internal ErrorType ErrorCode;

        // Only one constructor to discourage bad uFW coding practices
        // (inherited code had another constructor with msg as parameter)
        internal PacketParser()
        {
            Message = "";
        }

        // Encouraged to call this every time new message is received
        // instead of creating new PacketParser each time
        internal void GetPacket(string msg)
        {
            Message = msg;
            ParseMe();
        }

        // Parse message data
        // This function assumes the desired message is in Message
        // At termination, resp holds the response which is of length
        // respLen, ErrorCode contains the ErrorType associated with
        // Message and ErrorMessage contains the corresponding string,
        // and ResMsg contains the entire message
        internal void ParseMe()
        {
            // No response
            if ((Message == "") || (Message == null))
            {
                ResMsg = "";
                ErrorMessage = "No response";
                ErrorCode = ErrorType.NoResponse;
            }
            // ACK received, response falls into infinite loop
            else if (Message == ACK)
            {
                ResMsg = ACK;
                ErrorMessage = "Timeout expired & no data received." + "\r\n" + "Increasing timeout may help. ";
                ErrorCode = ErrorType.Timeout;
            }
            //NACK received
            else if (Message == NACK)
            {
                ResMsg = NACK;
                ErrorMessage = "NACK limit reached";
                ErrorCode = ErrorType.NACK;
            }
            // Illegal received message
            // Minimum length of a legal message is 8
            // 
            //   24  PCB  Length  LRC
            //   (2) (2)   (2)    (2)     = 8 bytes total
            else if (Message.Length < 8)
            {
                ResMsg = Message;
                ErrorMessage = "Illegal message";
                ErrorCode = ErrorType.IllegalMessage;
            }
            // Illegal sent message
            // R-Block was received, indicating error in sent message
            else if (Message.Substring(2, 2).CompareTo("80") >= 0)
            {
                ResMsg = Message;
                ErrorMessage = "Illegal message";
                ErrorCode = ErrorType.IllegalMessage;

                if (ResMsg.Length >= LenLocation + 2)
                {
                    string respLenStr = ResMsg.Substring(LenLocation, 2);
                    respLen = HexStringDecoder.HexStringToInt(respLenStr);
                    // Don't count status word as in response
                    // then change from byte length to string length
                    respLen--;
                    respLen *= 2;
                    if (respLen < 0)
                    {
                        respLen = 0;
                    }
                }
                else
                {
                    respLen = 0;
                }
                // Get status word if one was received
                if (ResMsg.Length >= LenLocation + 2 + statusWordStrLen)
                {
                    statusWord = ResMsg.Substring(LenLocation + 2, statusWordStrLen);
                }
                // Otherwise return incorrect data status
                else
                {
                    statusWord = INCORRECT_DATA;
                }
                // Get response if one was received
                if (respLen != 0 && ResMsg.Length >= LenLocation + 2 + statusWordStrLen + respLen)
                {
                    resp = ResMsg.Substring(LenLocation + 2 + statusWordStrLen, respLen);
                }
                else
                {
                    resp = null;
                }
            }
            // Parse data to see if there's any error
            else
            {
                ResMsg = Message;
                // Condition statements only hold if not trying to implement 
                // "Set Delay", "Restart", or "Set Reader in Halt" mode commands
                if ((ResMsg.Substring(4, 2) == "00"))
                {
                    // Don't know if 0 length is result of error or correct response
                    ErrorCode = ErrorType.Unknown;
                    statusWord = "FF";
                    respLen = 0;
                    resp = null;
                }
                else if ((ResMsg.Substring(6, 2) != "00"))
                {
                    // Example: 240001FB...,  Message.Substring(6, 2) = 'FB'
                    // Handle Error
                    // Get the error code, for example 'FB'
                    // Parse ResMsg to understand the error type
                    // Message.Substring(6, 2)
                    // Example: ResMsg.Substring(6, 2) = 'FB'
                    ErrorMessage = ResMsg.Substring(6, 2);
                    switch (ResMsg.Substring(6, 2))
                    {
                        case UNKNOWN_CMD:
                            ErrorCode = ErrorType.UnknownCommand;
                            break;
                        case OP_IMPOSSIBLE:
                            ErrorCode = ErrorType.OperationImpossible;
                            break;
                        case BAD_NUM_ARGS:
                            ErrorCode = ErrorType.NumberArgs;
                            break;
                        case BAD_CMD_CODE:
                            ErrorCode = ErrorType.BadCommandCode;
                            break;
                        case RESPONSE_TOO_LONG:
                            ErrorCode = ErrorType.ResponseLong;
                            break;
                        case BAD_ATR_TS:
                            ErrorCode = ErrorType.BadATRTS;
                            break;
                        case MSG_TOO_LONG:
                            ErrorCode = ErrorType.MessegeLong;
                            break;
                        case BYTE_READ_ERR:
                            ErrorCode = ErrorType.ByteRead;
                            break;
                        case POWERED_DOWN:
                            ErrorCode = ErrorType.PoweredDown;
                            break;
                        case BAD_NUM_PARAMS:
                            ErrorCode = ErrorType.NumberParams;
                            break;
                        case WRITING_OVERLAP:
                            ErrorCode = ErrorType.WritingOverlap;
                            break;
                        case BAD_ATR_TCK:
                            ErrorCode = ErrorType.BadATRTCK;
                            break;
                        case WRITE_PROTECTED:
                            ErrorCode = ErrorType.WriteProtected;
                            break;
                        case INCORRECT_DATA:
                            ErrorCode = ErrorType.IncorrectData;
                            break;
                        case RESET_RESPONSE_ERR:
                            ErrorCode = ErrorType.ResetResponse;
                            break;
                        case BAD_PROTOCOL:
                            ErrorCode = ErrorType.BadProcedure;
                            break;
                        case CARD_MALFUNCTION:
                            ErrorCode = ErrorType.CardMalfunction;
                            break;
                        case BAD_PARITY:
                            ErrorCode = ErrorType.BadParity;
                            break;
                        case CARD_ABORT:
                            ErrorCode = ErrorType.CardAbort;
                            break;
                        case READER_ABORT:
                            ErrorCode = ErrorType.ReaderAbort;
                            break;
                        case RESYNCH_SUCCESS:
                            ErrorCode = ErrorType.RESYNCHSuccess;
                            break;
                        case BAD_PTS:
                            ErrorCode = ErrorType.BadPTS;
                            break;
                        case CMD_NOT_SUPPORTED:
                            ErrorCode = ErrorType.CommandNotSupported;
                            break;
                        case OTHER_KEY_PRESSED:
                            ErrorCode = ErrorType.KeyPressed;
                            break;
                        case BAD_PROCEDURE:
                            ErrorCode = ErrorType.BadProcedure;
                            break;
                        case CARD_INTERUPTED:
                            ErrorCode = ErrorType.CardInterupted;
                            break;
                        case SW1_SW2:
                            ErrorCode = ErrorType.SW1SW2;
                            break;
                        case CARD_REMOVED:
                            ErrorCode = ErrorType.CardRemoved;
                            break;
                        case SHORT_CIRCUIT:
                            ErrorCode = ErrorType.ShortCircuit;
                            break;
                        case CARD_MISSING:
                            ErrorCode = ErrorType.CardMissing;
                            break;
                        default:
                            ErrorCode = ErrorType.Unknown;
                            break;
                    }
                    // If there is response, get its length
                    if (ResMsg.Length >= LenLocation + 2)
                    {
                        string respLenStr = ResMsg.Substring(LenLocation, 2);
                        respLen = HexStringDecoder.HexStringToInt(respLenStr);
                        respLen--;
                        respLen *= 2;
                    }
                    // If there is response, get it
                    if (ResMsg.Length >= LenLocation + 2 + respLen)
                    {
                        resp = ResMsg.Substring(LenLocation + 2 + statusWordStrLen, respLen);
                    }
                    // If there is a status word, get it
                    if (ResMsg.Length >= LenLocation + 2 + statusWordStrLen)
                    {
                        statusWord = ResMsg.Substring(LenLocation + 2, statusWordStrLen);
                    }

                }
                else
                {
                    string respLenStr = ResMsg.Substring(LenLocation, 2);
                    respLen = HexStringDecoder.HexStringToInt(respLenStr);
                    // Don't count status word as in response
                    // then change from byte length to string length
                    respLen--;
                    respLen *= 2;
                    resp = ResMsg.Substring(LenLocation + 2 + statusWordStrLen, respLen);
                    statusWord = ResMsg.Substring(LenLocation + 2, statusWordStrLen);
                    ErrorMessage = "";
                    ErrorCode = ErrorType.None;
                }
            }
        }
    }
}
